﻿using Black.Minecraft.Editor.Models;
using Black.Minecraft.Editor.ViewModels.Core;
using Black.NBT;
using System;
using System.Linq;

namespace Black.Minecraft.Editor.ViewModels.DataModels
{
    /// <summary>
    /// DataModel pour un item de l'inventaire
    /// </summary>
    public class InventoryItemDataModel : BaseViewModel
    {
        #region Propriétés

        /// <summary>
        /// Obtient ou définit le nombre de l'élément
        /// </summary>
        public byte Count
        {
            get
            {
                return ((this._data["Count"] as TagByte).Value);
            }
            set
            {
                if ((this._data["Count"] as TagByte).Value != value)
                {
                    (this._data["Count"] as TagByte).Value = value;
                    this.OnPropertyChanged();
                    this.OnPropertyChanged("IsNotSingle");
                    MainViewModel.Instance.IsModified = true;
                }
            }
        }

        /// <summary>
        /// Obtient ou définit les dommages de l'élément
        /// </summary>
        public short Damage
        {
            get
            {
                return ((this._data["Damage"] as TagShort).Value);
            }
            set
            {
                if ((this._data["Damage"] as TagShort).Value != value)
                {
                    (this._data["Damage"] as TagShort).Value = value;
                    this.OnPropertyChanged();
                    MainViewModel.Instance.IsModified = true;
                }
            }
        }

        /// <summary>
        /// Obtient la définition de l'objet
        /// </summary>
        public ItemDataModel Definition
        {
            get
            {
                return (this._itemDef);
            }
            set
            {
                if (this._itemDef != value)
                {
                    this._itemDef = value;
                    this.VerifyItem();
                    this.OnPropertyChanged();
                    MainViewModel.Instance.IsModified = true;
                }
            }
        }

        /// <summary>
        /// Obtient ou définit les informations d'enchantement
        /// </summary>
        public EnchantDataModel Enchant
        {
            get
            {
                return (this._enchant);
            }
            set
            {
                if (this._enchant != value)
                {
                    this._enchant = value;

                    if (!this._data.Contains("tag"))
                        this._data.Items.Add(this._enchant.GetData());

                    this.VerifyItem();

                    this.OnPropertyChanged();
                    MainViewModel.Instance.IsModified = true;
                }
            }
        }

        /// <summary>
        /// Obtient ou définit l'identifiant de l'élément
        /// </summary>
        public short ID
        {
            get
            {
                return ((this._data["id"] as TagShort).Value);
            }
            set
            {
                if ((this._data["id"] as TagShort).Value != value)
                {
                    (this._data["id"] as TagShort).Value = value;
                    this.OnPropertyChanged();
                    MainViewModel.Instance.IsModified = true;
                }
            }
        }

        /// <summary>
        /// Obtient si l'objet n'est pas un fake
        /// </summary>
        public bool IsNotChunk
        {
            get
            {
                return (this.ID > 0);
            }
        }

        /// <summary>
        /// Obtient ou définit si l'objet est en sélection
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return (this._isSelected);
            }
            set
            {
                if (this._isSelected != value)
                {
                    this._isSelected = value;
                    this.OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// Obtient si il n'y a qu'une occurance de l'item
        /// </summary>
        public bool IsNotSingle
        {
            get
            {
                return (this.Count > 1);
            }
        }

        /// <summary>
        /// Obtient ou définit le slot de l'élément
        /// </summary>
        public SlotsEnum Slot
        {
            get
            {
                return ((SlotsEnum)(this._data["Slot"] as TagByte).Value);
            }
            set
            {
                if ((SlotsEnum)(this._data["Slot"] as TagByte).Value != value)
                {
                    (this._data["Slot"] as TagByte).Value = (byte)value;
                    this.OnPropertyChanged();
                    this.GetItemDefinition();
                }
            }
        }

        /// <summary>
        /// Retourne le tooltip de l'item
        /// </summary>
        public string ToolTip
        {
            get
            {
                string tooltip = this.Definition.Name;

                if (this.Enchant != null)
                {
                    if (!string.IsNullOrEmpty(this.Enchant.DisplayName))
                        tooltip = this.Enchant.DisplayName;

                    tooltip += this.Enchant.ToolTip;
                }

                return (tooltip);
            }
        }

        #endregion

        #region Variables d'instances

        private bool _isSelected;
        private ItemDataModel _itemDef;
        private TagCompound _data;
        private EnchantDataModel _enchant;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        /// <param name="data">Source de donnée</param>
        public InventoryItemDataModel(SlotsEnum slot)
            : base()
        {
            // Initialisation de la source de donnée

            this._data = InventoryItemDataModel.CreateItemTag((short)-1, (short)0, slot);
            this.GetItemDefinition();
        }

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        /// <param name="data">Source de donnée</param>
        public InventoryItemDataModel(TagCompound data)
            : base()
        {
            // Initialisation de la source de donnée

            this._data = data;
            this.GetItemDefinition();

            // Récupération des enchangements

            if (this.Definition.CanEnchant && this._data.Contains("tag", TagType.Compound))
                this._enchant = new EnchantDataModel((TagCompound)this._data["tag"]);
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Retourne la source de donnée
        /// </summary>
        /// <returns>Instance de la source de donnée</returns>
        public TagCompound GetData()
        {
            // On renvoi les données

            return (this._data);
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Récupère la définition de l'objet
        /// </summary>
        private void GetItemDefinition()
        {
            // Recherche définition existante

            this._itemDef = (from p in MainViewModel.Instance.ItemsDefinition
                             where (p.Id == this.ID && p.CanRepair)
                             || (p.Id == this.ID && p.Damage == this.Damage)
                             select p).FirstOrDefault();

            // Si non trouvé on le crée

            if (this._itemDef == null)
            {
                this._itemDef = new ItemDataModel(new Item()
                {
                    CanEnchant = false,
                    CanRepair = false,
                    Custom = true,
                    Damage = this.Damage,
                    Id = this.ID,
                    MaxStack = 64,
                    Name = "Unkhown " + this.ID.ToString()
                });
            }

            // On notifie

            this.OnPropertyChanged("Definition");
        }

        /// <summary>
        /// Vérifie un changement d'objet
        /// </summary>
        private void VerifyItem()
        {
            this.ID = this._itemDef.Id;
            this.Damage = this._itemDef.CanRepair ? (short)0 : this._itemDef.Damage;
            this.Count = this.Count > this._itemDef.MaxStack ? this._itemDef.MaxStack : this.Count;

            if (!this.Definition.CanEnchant && this._enchant != null)
            {
                this._enchant = null;

                if (this._data.Contains("tag"))
                    this._data.Items.Remove(this._data["tag"]);
            }
        }

        #endregion

        #region Fonctions statiques

        /// <summary>
        /// Crée un tag Item
        /// </summary>
        /// <param name="id">Identifiant de l'objet</param>
        /// <param name="damage">Dommage de l'objet</param>
        /// <param name="slot">Slot de l'objet</param>
        /// <returns>Tag créé</returns>
        public static TagCompound CreateItemTag(short id, short damage, SlotsEnum slot)
        {
            TagCompound item = new TagCompound(string.Empty);

            // Création des sous tags

            TagShort idTag = new TagShort("id");
            idTag.Value = id;
            item.Items.Add(idTag);

            TagShort damageTag = new TagShort("Damage");
            damageTag.Value = damage;
            item.Items.Add(damageTag);

            TagByte countTag = new TagByte("Count");
            countTag.Value = (byte)1;
            item.Items.Add(countTag);

            TagByte slotTag = new TagByte("Slot");
            slotTag.Value = (byte)slot;
            item.Items.Add(slotTag);

            // On retourne l'instance

            return (item);
        }

        #endregion
    }
}
